package simpledb.execution;

import simpledb.common.Database;
import simpledb.common.DbException;
import simpledb.storage.DbFileIterator;
import simpledb.storage.HeapFile;
import simpledb.storage.Tuple;
import simpledb.storage.TupleDesc;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;
import simpledb.util.HeapFileIterator;

import java.util.ArrayList;
import java.util.NoSuchElementException;

/**
 * SeqScan 是一个顺序扫描访问方法的实现。
 * 它按无特定顺序读取表中的每个元组（例如：按照它们在磁盘上的存储顺序）。
 * 作为关系型数据库中最基础的查询算子，SeqScan 是实现全表扫描的核心组件。
 */
public class SeqScan implements OpIterator {

    private static final long serialVersionUID = 1L;

    // 当前事务ID，用于标识执行扫描的事务上下文
    private TransactionId     tid;

    // 被扫描的表的唯一标识符（在数据库目录中）
    private int               tableId;

    // 表的别名（用于SQL解析），例如：SELECT t1.name FROM users t1
    private String            tableAlias;

    // 用于遍历数据库文件页面的迭代器，实际类型通常为 HeapFileIterator
    private DbFileIterator    dbFileIterator;

    // 元组描述信息（包含字段类型和名称），使用表别名修饰字段名
    private TupleDesc         tupleDesc;

    /**
     * 创建一个顺序扫描操作，用于指定事务中对某张表的扫描。
     *
     * @param tid 此扫描所属的事务
     * @param tableid 要扫描的表的ID
     * @param tableAlias 表的别名（用于SQL解析），返回的tupleDesc中的字段应为tableAlias.fieldName格式
     *                   （注意：本类不负责处理tableAlias或fieldName为null的情况。如果为null，不会崩溃，
     *                   但结果可能是 null.fieldName、tableAlias.null 或 null.null）
     */
    public SeqScan(TransactionId tid, int tableid, String tableAlias) {
        this.tid = tid;
        this.tableId = tableid;
        this.tableAlias = tableAlias;
        // 获取对应表的数据库文件，并初始化其迭代器
        this.dbFileIterator = Database.getCatalog().getDatabaseFile(tableid).iterator(tid);
    }

    /**
     * 返回当前操作所扫描的表的表名。
     * 这应该是数据库目录中该表的实际名称。
     *
     * @return 表的真实名称
     */
    public String getTableName() {
        return Database.getCatalog().getTableName(this.tableId);
    }

    /**
     * 返回当前操作所扫描的表的别名。
     *
     * @return 表的别名
     */
    public String getAlias() {
        return this.tableAlias;
    }

    /**
     * 重置当前扫描的表ID和别名。
     *
     * @param tableid 新的要扫描的表ID
     * @param tableAlias 新的表别名（用于SQL解析）
     */
    public void reset(int tableid, String tableAlias) {
        this.tableId = tableid;
        this.tableAlias = tableAlias;
        this.tupleDesc = null; // 清除旧的tupleDesc缓存

        // 获取新的表对象
        final HeapFile dbFile = (HeapFile) Database.getCatalog().getDatabaseFile(tableid);

        // 重新创建迭代器
        this.dbFileIterator = new HeapFileIterator(dbFile.numPages(), tid, tableId);
    }

    /**
     * 构造函数重载版本：如果不提供别名，则使用默认的表名作为别名
     */
    public SeqScan(TransactionId tid, int tableId) {
        this(tid, tableId, Database.getCatalog().getTableName(tableId));
    }

    /**
     * 打开迭代器，准备开始遍历数据
     */
    @Override
    public void open() throws DbException, TransactionAbortedException {
        this.dbFileIterator.open();
    }

    /**
     * 判断是否还有更多元组可以读取
     */
    @Override
    public boolean hasNext() throws TransactionAbortedException, DbException {
        return this.dbFileIterator.hasNext();
    }

    /**
     * 返回下一个元组
     */
    @Override
    public Tuple next() throws NoSuchElementException, TransactionAbortedException, DbException {
        final Tuple next = this.dbFileIterator.next();

        // 创建一个新的元组，使用带别名的tupleDesc
        final Tuple result = new Tuple(getTupleDesc());

        // 复制字段值
        for (int i = 0; i < next.getTupleDesc().numFields(); i++) {
            result.setField(i, next.getField(i));
        }

        // 设置记录ID
        result.setRecordId(next.getRecordId());
        return result;
    }

    /**
     * 将迭代器重置到初始状态
     */
    @Override
    public void rewind() throws DbException, NoSuchElementException, TransactionAbortedException {
        this.dbFileIterator.rewind();
    }

    /**
     * 返回带有别名修饰的TupleDesc对象。
     * 字段名格式为 tableAlias.fieldName，这在连接多个包含相同字段名的表时非常有用。
     *
     * @return 带有别名修饰的TupleDesc对象
     */
    @Override
    public TupleDesc getTupleDesc() {
        if (this.tupleDesc != null) {
            return this.tupleDesc;
        }

        // 获取原始表的TupleDesc
        final TupleDesc td = Database.getCatalog().getTupleDesc(this.tableId);

        // 创建新的TDItem列表，添加别名前缀
        final ArrayList<TupleDesc.TDItem> tdItems = new ArrayList<>();
        for (final TupleDesc.TDItem item : td.getDescList()) {
            // 处理可能为null的字段名和别名
            final String fieldName = (this.tableAlias == null ? "null." : this.tableAlias) + "."
                                     + (item.fieldName == null ? "null" : item.fieldName);
            tdItems.add(new TupleDesc.TDItem(item.fieldType, fieldName));
        }

        // 创建新的TupleDesc
        this.tupleDesc = new TupleDesc(tdItems);
        return this.tupleDesc;
    }

    /**
     * 关闭迭代器，释放资源
     */
    @Override
    public void close() {
        this.dbFileIterator.close();
    }
}
